﻿using Longbow.Cache;
using Longbow.Web;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Linq;
using System.Net;

namespace Microsoft.AspNetCore.Builder
{
    /// <summary>
    /// 当前用户统计中间件
    /// </summary>
    public static class OnlineUsersMiddlewareExtensions
    {
        /// <summary>
        /// 添加 当前用户中间件
        /// </summary>
        /// <param name="builder">IApplicationBuilder 实例</param>
        /// <param name="filter">请求地址过滤器，返回真时进入在线用户中间件</param>
        /// <param name="callback">在线用户回调方法，用于调用方扩展</param>
        /// <returns></returns>
        public static IApplicationBuilder UseOnlineUsers(this IApplicationBuilder builder, Func<HttpContext, bool> filter = null, Action<HttpContext, OnlineUser> callback = null) => builder.UseWhen(context => filter == null ? context.Filter() : filter.Invoke(context), app => app.Use(async (context, next) =>
        {
            await System.Threading.Tasks.Task.Run(() =>
            {
                var onlineUserSvr = context.RequestServices.GetRequiredService<IOnlineUsers>();
                var locator = context.RequestServices.GetRequiredService<IIPLocatorProvider>();
                var proxy = new Func<AutoExpireCacheEntry<OnlineUser>, Action, AutoExpireCacheEntry<OnlineUser>>((c, action) =>
                {
                    var v = c.Value;
                    v.LastAccessTime = DateTime.Now;
                    v.Method = context.Request.Method;
                    v.RequestUrl = context.Request.Path;
                    v.AddRequestUrl(context.Request.Path);
                    action?.Invoke();
                    callback?.Invoke(context, v);
                    return c;
                });
                onlineUserSvr.AddOrUpdate(context.Connection.Id ?? "", key =>
                {
                    var v = new OnlineUser();
                    v.UserAgent = context.Request.Headers["User-Agent"];
                    var agent = string.IsNullOrEmpty(v.UserAgent) ? null : new UserAgent(v.UserAgent);
                    v.ConnectionId = key;
                    v.Ip = context.Connection.RemoteIpAddress.ToIPv4String();
                    v.Location = locator?.Locate(v.Ip);
                    v.Browser = agent == null ? "Unknown" : $"{agent.Browser?.Name} {agent.Browser?.Version}";
                    v.OS = agent == null ? "Unknown" : $"{agent.OS?.Name} {agent.OS?.Version}";
                    v.FirstAccessTime = DateTime.Now;
                    v.Referer = context.Request.Headers["Referer"];
                    return proxy(new AutoExpireCacheEntry<OnlineUser>(v, 1000 * 60, __ => onlineUserSvr.TryRemove(key, out _)), null);
                }, (key, v) => proxy(v, () => v.Reset()));
            });
            await next();
        }));

        private static bool Filter(this HttpContext context)
        {
            var url = context.Request.Path;
            return !new string[] { "/api", "/NotiHub", "/swagger" }.Any(r => url.StartsWithSegments(r, StringComparison.OrdinalIgnoreCase));
        }
    }
}
